package org.mozhu.mboot.core.config.support;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.mozhu.mboot.core.config.ApplicationConfiguration;
import org.mozhu.mboot.core.config.ConfigurationListener;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@Slf4j
public class BeanConfigurableProperty implements ConfigurationListener {

    private ApplicationConfiguration configuration;
    private String key;
    private Object bean;
    private Method updateMethod;

    public BeanConfigurableProperty() {
    }

    public BeanConfigurableProperty(String key, Object bean, Method updateMethod) {
        this.key = key;
        this.bean = bean;
        this.updateMethod = updateMethod;
    }

    public void invokeSetValue(String value) throws IllegalAccessException, InvocationTargetException {
        log.debug("set config property {} of bean {} using method {}", new Object[]{key, bean, updateMethod.getName()});
        Object bean = getBean();
        Class<?> parameterOfMethod = updateMethod.getParameterTypes()[0];
        if (parameterOfMethod.equals(String.class)) {
            updateMethod.invoke(bean, value);
            return;
        }
        if (parameterOfMethod.isPrimitive()) {
            parameterOfMethod = ClassUtils.primitiveToWrapper(parameterOfMethod);
        }
        Method valueOfMethod = getValueOfMethod(parameterOfMethod);
        if (valueOfMethod != null) {
            updateMethod.invoke(bean, valueOfMethod.invoke(null, value));
            return;
        }
        Constructor<?> constructor = getValueConstructor(parameterOfMethod);
        if (constructor != null) {
            updateMethod.invoke(bean, newInstance(value, constructor));
            return;
        }
    }

    private Object newInstance(String value, Constructor<?> constructor) throws IllegalAccessException, InvocationTargetException {
        try {
            return constructor.newInstance(value);
        } catch (InstantiationException e) {
            throw new IllegalStateException(e);
        }
    }

    private Constructor<?> getValueConstructor(Class<?> parameterOfMethod) {
        try {
            return parameterOfMethod.getConstructor(String.class);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    private static Method getValueOfMethod(Class<?> clazz) {
        try {
            return clazz.getMethod("valueOf", String.class);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "Bean " + bean.getClass() + ", property update method " + updateMethod.getName() + " with key " + key;
    }

    public String getKey() {
        return key;
    }

    public Object getBean() {
        return bean;
    }

    public Method getUpdateMethod() {
        return updateMethod;
    }

    public void setApplicationConfiguration(ApplicationConfiguration configuration) {
        this.configuration = configuration;
        this.configuration.addListener(this);
    }

    @Override
    public void notifyUpdate(String key) {
        if (!this.key.equals(key)) {
            return;
        }
        String value = configuration.getProperty(key);
        try {
            invokeSetValue(value);
        } catch (Exception e) {
            log.error("update value [0] failed for [1]", new Object[]{value, this.toString()});
        }
    }
}
